package webScoket

import (
	"github.com/gorilla/websocket"
	"net/http"
	"time"
)

type WebScoketServer struct {
	options *WebScoketOptions

	ConnFunc    func(conn *websocket.Conn, broadcast chan []byte)
	DisConnFunc func(conn *websocket.Conn)
	ReadFunc    func(conn *websocket.Conn, message []byte)
}

func NewWebScoket(opts []WebScoketOption) *WebScoketServer {
	server := &WebScoketServer{
		options: &WebScoketOptions{},
	}

	for _, o := range opts {
		o(server.options)
	}

	return server
}

func (s *WebScoketServer) Serve() error {
	http.HandleFunc(s.options.Pattern, func(w http.ResponseWriter, r *http.Request) {
		s.serveWs(w, r)
	})

	err := http.ListenAndServe(s.options.Addr, nil)
	return err
}

func (s *WebScoketServer) serveWs(w http.ResponseWriter, r *http.Request) {
	var upGrader = websocket.Upgrader{
		ReadBufferSize:  s.options.ReadBufferSize,
		WriteBufferSize: s.options.WriteBufferSize,
	}

	upGrader.CheckOrigin = func(r *http.Request) bool { return true }

	conn, err := upGrader.Upgrade(w, r, nil)
	if err != nil {
	}

	broadcast := make(chan []byte, s.options.BroadcastSize)

	s.ConnFunc(conn, broadcast)

	go s.writePump(conn)
	go s.readPump(conn, broadcast)
}

func (s *WebScoketServer) writePump(conn *websocket.Conn) {
	defer func() {
		conn.Close()
	}()

	conn.SetReadLimit(s.options.MaxMessageSize)
	conn.SetReadDeadline(time.Now().Add(s.options.PongWait))
	conn.SetPongHandler(func(string) error {
		conn.SetReadDeadline(time.Now().Add(s.options.PongWait))
		return nil
	})

	for {
		_, message, err := conn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				// todo 记录异常错误信息
				return
			}
		}

		if message == nil {
			return
		}

		s.ReadFunc(conn, message)
	}
}

func (s *WebScoketServer) readPump(conn *websocket.Conn, broadcast <-chan []byte) {
	ticker := time.NewTicker(s.options.PingPeriod)
	defer func() {
		ticker.Stop()
		s.DisConnFunc(conn)
		conn.Close()
	}()

	for {
		select {
		case msg, ok := <-broadcast:
			conn.SetWriteDeadline(time.Now().Add(s.options.WriteWait))
			if !ok {
				conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}

			w, err := conn.NextWriter(websocket.TextMessage)
			if err != nil {
				return
			}

			w.Write(msg)
			if w.Close(); err != nil {
				return
			}
		case <-ticker.C:
			conn.SetWriteDeadline(time.Now().Add(s.options.WriteWait))
			if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}
